package com.doudou.mybatis.mate.core.interceptors;

import com.doudou.mybatis.mate.core.dialect.IDialectHandler;
import com.doudou.mybatis.mate.core.entity.Page;
import com.doudou.mybatis.mate.core.helper.PageHelper;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.util.*;

/**
 * PageInterceptor
 *
 * @author jiangcs
 * @since 2022/3/30
 */
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class PageInterceptor implements Interceptor {
    private volatile IDialectHandler dialectHandler;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 执行器 Executor
        Executor executor = (Executor) invocation.getTarget();
        // 获取拦截方法的参数，获取到的是一个数组
        Object[] args = invocation.getArgs();
        // 第一个：MappedStatement，*Mapper.xml文件中定义的各种操作节点和 SQL，都被封装成一个个的 MappedStatement 对象
        MappedStatement mappedStatement = (MappedStatement) args[0];
        // 第二个：拦截方法的具体参数，*Mapper接口中定义的方法参数
        Object parameterObject = args[1];
        // 第三个：RowBounds，*Mapper接口中定义方法时若没定义 RowBounds，系统会给我们提供一个默认的 RowBounds.DEFAULT
        RowBounds rowBounds = (RowBounds) args[2];
        // 第四个：处理返回值的 ResultHandler
        ResultHandler resultHandler = (ResultHandler) args[3];

        // BoundSql：封装了执行的 Sql 以及相关的参数
        BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
        // 通过反射拿出来 BoundSql 中保存的额外参数（如果我们使用了动态 SQL，可能会存在该参数）
        Field additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
        additionalParametersField.setAccessible(true);
        Map<String, Object> additionalParameterMap = (Map<String, Object>) additionalParametersField.get(boundSql);

        Page page = PageHelper.getPage(rowBounds, parameterObject);
        if (page != null) {
            // Count SQL
            BoundSql countBoundSql = dialectHandler.getCountSql(mappedStatement, parameterObject, boundSql, additionalParameterMap);
            // 记录总数 MappedStatement
            MappedStatement countMappedStatement = countMappedStatement(mappedStatement);
            // 记录总数缓存 Key
            CacheKey countCacheKey = executor.createCacheKey(countMappedStatement, parameterObject, rowBounds, countBoundSql);
            // 查询 Count
            List<Object> countQueryResult = executor.query(countMappedStatement, parameterObject, rowBounds, resultHandler, countCacheKey, countBoundSql);
            long count = (Long) countQueryResult.get(0);
            page.setTotal(count);
            // count <= 0，返回 空集合
            if (count <= 0) {
                return Collections.emptyList();
            }

            // 分页 SQL
            BoundSql pageBoundSql = dialectHandler.getPageSql(mappedStatement, parameterObject, boundSql, additionalParameterMap, page);
            // 分页缓存 Key
            CacheKey cachePageKey = executor.createCacheKey(mappedStatement, parameterObject, rowBounds, pageBoundSql);
            // 查询 分页
            return executor.query(mappedStatement, parameterObject, rowBounds, resultHandler, cachePageKey, pageBoundSql);
        }
        return invocation.proceed();
    }

    /**
     * 查询记录总数 MappedStatement
     *
     * @param mappedStatement
     * @return
     */
    private MappedStatement countMappedStatement(MappedStatement mappedStatement) {
        MappedStatement.Builder builder = new MappedStatement.Builder(
                mappedStatement.getConfiguration(),
                mappedStatement.getId() + "_count",
                mappedStatement.getSqlSource(),
                mappedStatement.getSqlCommandType()
        )
                .resource(mappedStatement.getResource())
                .fetchSize(mappedStatement.getFetchSize())
                .statementType(mappedStatement.getStatementType())
                .timeout(mappedStatement.getTimeout())
                .parameterMap(mappedStatement.getParameterMap())
                .resultSetType(mappedStatement.getResultSetType())
                .cache(mappedStatement.getCache())
                .flushCacheRequired(mappedStatement.isFlushCacheRequired())
                .useCache(mappedStatement.isUseCache())
                .resultMaps(List.of(
                                new ResultMap.Builder(
                                        mappedStatement.getConfiguration(),
                                        mappedStatement.getId(),
                                        Long.class,
                                        new ArrayList<>()
                                ).build()
                        )
                );

        if (mappedStatement.getKeyProperties() != null && mappedStatement.getKeyProperties().length > 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : mappedStatement.getKeyProperties()) {
                if (keyProperties.length() > 0) {
                    keyProperties.append(",");
                }
                keyProperties.append(keyProperty);
            }
            builder.keyProperty(keyProperties.toString());
        }
        return builder.build();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        if (!properties.isEmpty()) {
            dialectHandler = (IDialectHandler) properties.get("dialectHandler");
        }
    }
}
